/*
 * RulesManager.cpp
 *
 *  Created on: Dec 18, 2013
 *      Author: artech
 */

#include "RulesManager.h"
#include "Sdq.h"
#include "SdqException.h"

RulesManager::RulesManager()
{
}

RulesManager::~RulesManager()
{
}

void RulesManager::initialize(QMap<QPair<quint8, quint8>, quint8> grid)
{
   clear();
   for (QMap<QPair<quint8, quint8>, quint8>::iterator squareIt = grid.begin();
         squareIt != grid.end(); ++squareIt)
   {
      quint8 lineNb = squareIt.key().first;
      quint8 columnNb = squareIt.key().second;
      quint8 value = *squareIt;
      if (lineNb >= GRID_SIZE || columnNb >= GRID_SIZE
            || value < 1|| value > GRID_SIZE)
      {
         throw SdqException(
               QString("Bad input %1-%2:%3").arg(lineNb).arg(columnNb).arg(
                     value));
      }
      treatOnlyOne(_gridModel.getSquareModel(lineNb, columnNb), value);
   }
}

void RulesManager::applyRules()
{
   printGrid();
   quint64 count(0);
   while (!_squareToTreatStack.empty())
   {
      SquareModel* squareModel = _squareToTreatStack.pop();
      if (squareModel->doesValueBeFound())
      {
         //Only for known square
         treatLine(squareModel);
         treatColumn(squareModel);
         treatSquare9(squareModel);
         treatPivotLine(squareModel);
         treatPivotColumn(squareModel);
         ++count;
      }
   }
   printGrid();
}
/*
 *Rule : all the square on the same line can not have the same value.
 */
void RulesManager::treatLine(SquareModel* squareModel)
{
   quint8 lineNb = squareModel->getLineNb();
   quint8 columnNb = squareModel->getColumnNb();
   for (quint8 j = 0; j < GRID_SIZE; ++j)
   {
      if (j != columnNb)
      {
         treatSquare(lineNb, j, squareModel->getValue());
      }
   }
}

/*
 *Rule : all the square on the same column can not have the same value.
 */
void RulesManager::treatColumn(SquareModel* squareModel)
{
   quint8 lineNb = squareModel->getLineNb();
   quint8 columnNb = squareModel->getColumnNb();
   for (quint8 i = 0; i < GRID_SIZE; ++i)
   {
      if (i != lineNb)
      {
         treatSquare(i, columnNb, squareModel->getValue());
      }
   }
}

/*
 *Definition : A square9 is a square composed with 9 squares
 *             A sudoku grid is composed with 9 square9
 *Rule : all the square on the same square9 can not have the same value.
 */
void RulesManager::treatSquare9(SquareModel* squareModel)
{
   quint8 lineNb = squareModel->getLineNb();
   quint8 columnNb = squareModel->getColumnNb();
   quint8 baseLineNb = squareModel->getBaseLineNb();
   quint8 baseColumnNb = squareModel->getBaseColumnNb();
   for (quint8 treatedLineNb = baseLineNb;
         treatedLineNb < baseLineNb + SQUARE_9_SIZE; ++treatedLineNb)
   {
      for (quint8 treatedColumnNb = baseColumnNb;
            treatedColumnNb < baseColumnNb + SQUARE_9_SIZE; ++treatedColumnNb)
      {
         if (treatedLineNb != lineNb || treatedColumnNb != columnNb)
         {
            treatSquare(treatedLineNb, treatedColumnNb,
                  squareModel->getValue());
         }
      }
   }
}

/*
 *Rule : If a pivot exists on a line (tree case with known value in the same 9square
 *       and on the same line)
 *       In all the other square that TODO(find a good definition),
 *       if the value is not in the pivot, it can not be on the opposite TODO(find a good definition)
 */
void RulesManager::treatPivotLine(SquareModel* squareModel)
{
   quint8 pivotLineNb = squareModel->getLineNb();
   quint8 columnNb = squareModel->getColumnNb();
   quint8 pivotBaseColumnNb = squareModel->getBaseColumnNb();
   QSet<quint8> pivotValueSet;
   for (quint8 j = pivotBaseColumnNb; j < pivotBaseColumnNb + SQUARE_9_SIZE;
         ++j)
   {
      if (_gridModel.getSquareModel(pivotLineNb, j)->doesValueBeFound())
      {
         pivotValueSet.insert(
               _gridModel.getSquareModel(pivotLineNb, j)->getValue());
      }
   }
   if (pivotValueSet.size() == SQUARE_9_SIZE)
   {
      //It is a pivot line
      quint8 pivotBaseLineNb = squareModel->getBaseLineNb();
      for (quint8 i = pivotBaseLineNb; i < pivotBaseLineNb + SQUARE_9_SIZE; ++i)
      {
         if (i != pivotLineNb)
         {
            for (quint8 j = 0; j < GRID_SIZE; ++j)
            {
               quint8 currentBaseColumnNb = j - j % SQUARE_9_SIZE;
               if (currentBaseColumnNb != pivotBaseColumnNb)
               {
                  //For all the other square that TODO(find a good definition)
                  if (_gridModel.getSquareModel(i, j)->doesValueBeFound()
                        && pivotValueSet.find(
                              _gridModel.getSquareModel(i, j)->getValue())
                              == pivotValueSet.end())
                  {
                     //if value is not in the pivot
                     //Find the opposite line
                     quint8 lineNbToTreat = (i + 1) % SQUARE_9_SIZE
                           + pivotBaseLineNb;
                     if (lineNbToTreat == pivotLineNb)
                     {
                        lineNbToTreat = (i + 2) % SQUARE_9_SIZE
                              + pivotBaseLineNb;
                     }
                     //Find the opposite column base
                     quint8 baseColumnNbToTreat = (currentBaseColumnNb
                           + SQUARE_9_SIZE) % GRID_SIZE;
                     if (baseColumnNbToTreat == pivotBaseColumnNb)
                     {
                        baseColumnNbToTreat = (currentBaseColumnNb
                              + 2 * SQUARE_9_SIZE) % GRID_SIZE;
                     }
                     for (quint8 columnNbToTreat = baseColumnNbToTreat;
                           columnNbToTreat < baseColumnNbToTreat + SQUARE_9_SIZE;
                           ++columnNbToTreat)
                     {
                        treatSquare(lineNbToTreat, columnNbToTreat,
                              _gridModel.getSquareModel(i, j)->getValue());
                     }
                  }
               }
            }
         }
      }
   }
}

/*
 *Rule : Same than treatPivotLine but for pivot column
 */
void RulesManager::treatPivotColumn(SquareModel* squareModel)
{
   quint8 lineNb = squareModel->getLineNb();
   quint8 pivotColumnNb = squareModel->getColumnNb();
   quint8 pivotBaseLineNb = squareModel->getBaseLineNb();
   QSet<quint8> pivotValueSet;
   for (quint8 i = pivotBaseLineNb; i < pivotBaseLineNb + SQUARE_9_SIZE; ++i)
   {
      if (_gridModel.getSquareModel(i, pivotColumnNb)->doesValueBeFound())
      {
         pivotValueSet.insert(
               _gridModel.getSquareModel(i, pivotColumnNb)->getValue());
      }
   }
   if (pivotValueSet.size() == SQUARE_9_SIZE)
   {
      //If a pivot is found
      quint8 pivotBaseColumnNb = squareModel->getBaseColumnNb();
      for (quint8 j = pivotBaseColumnNb; j < pivotBaseColumnNb + SQUARE_9_SIZE;
            ++j)
      {
         if (j != pivotColumnNb)
         {
            for (quint8 i = 0; i < GRID_SIZE; ++i)
            {
               quint8 currentBaseLineNb = i - i % SQUARE_9_SIZE;
               if (currentBaseLineNb != pivotBaseLineNb)
               {
                  if (_gridModel.getSquareModel(i, j)->doesValueBeFound()
                        && pivotValueSet.find(
                              _gridModel.getSquareModel(i, j)->getValue())
                              == pivotValueSet.end())
                  {
                     //if value is not in the pivot
                     //Find the opposite column
                     quint8 columnNbToTreat = (j + 1) % SQUARE_9_SIZE
                           + pivotBaseColumnNb;
                     if (columnNbToTreat == pivotColumnNb)
                     {
                        columnNbToTreat = (j + 2) % SQUARE_9_SIZE
                              + pivotBaseColumnNb;
                     }

                     //Find the opposite line base
                     quint8 baseLineNbToTreat = (currentBaseLineNb
                           + SQUARE_9_SIZE) % GRID_SIZE;
                     if (baseLineNbToTreat == pivotBaseLineNb)
                     {
                        baseLineNbToTreat = (currentBaseLineNb
                              + 2 * SQUARE_9_SIZE) % GRID_SIZE;
                     }
                     for (quint8 lineNbToTreat = baseLineNbToTreat;
                           lineNbToTreat < baseLineNbToTreat + SQUARE_9_SIZE;
                           ++lineNbToTreat)
                     {
                        treatSquare(lineNbToTreat, columnNbToTreat,
                              _gridModel.getSquareModel(i, j)->getValue());
                     }
                  }
               }
            }
         }
      }
   }
}

/*
 *Value is not a solution for square lineNb-columnNb
 */
void RulesManager::treatSquare(quint8 lineNb, quint8 columnNb, quint8 value)
{
   SquareModel* treatedSquare = _gridModel.getSquareModel(lineNb, columnNb);
   if (treatedSquare->popValue(value))
   {
      treatOnlyOneInLine(treatedSquare, value);
      treatOnlyOneInColumn(treatedSquare, value);
      treatOnlyOneInSquare9(treatedSquare, value);
      if (treatedSquare->doesValueBeFound())
      {
         //If solution if found, add this square in the treatment stack
         _squareToTreatStack.push_front(treatedSquare);
      }
   }
}

/*
 *Rule : Check if a solution is only present once in the given line
 */
void RulesManager::treatOnlyOneInLine(SquareModel* squareModel, quint8 value)
{
   quint8 lineNb = squareModel->getLineNb();
   if (_histogramValueForLine[lineNb][value - 1].remove(squareModel))
   {
      switch (_histogramValueForLine[lineNb][value - 1].size())
      {
      case 0:
         throw SdqException(
               QString("No square possible for value %1 in line %2").arg(value).arg(
                     lineNb));
         break;
      case 1:
         treatOnlyOne(*_histogramValueForLine[lineNb][value - 1].begin(),
               value);
         break;
      default:
         break;
      }
   }
}

/*
 *Rule : Check if a solution is only present once in the given column
 */
void RulesManager::treatOnlyOneInColumn(SquareModel* squareModel, quint8 value)
{
   quint8 columnNb = squareModel->getColumnNb();
   if (_histogramValueForColumn[columnNb][value - 1].remove(squareModel))
   {
      switch (_histogramValueForColumn[columnNb][value - 1].size())
      {
      case 0:
         throw SdqException(
               QString("No square possible for value %1 in column %2").arg(
                     value).arg(columnNb));
         break;
      case 1:
         treatOnlyOne(*_histogramValueForColumn[columnNb][value - 1].begin(),
               value);
         break;
      default:
         break;
      }
   }
}

/*
 *Rule : Check if a solution is only present once in the given square9
 */
void RulesManager::treatOnlyOneInSquare9(SquareModel* squareModel, quint8 value)
{
   quint8 square9Nb = squareModel->getSquare9Nb();
   if (_histogramValueForSquare9[square9Nb][value - 1].remove(squareModel))
   {
      switch (_histogramValueForSquare9[square9Nb][value - 1].size())
      {
      case 0:
         throw SdqException(
               QString("No square possible for value %1 in square9 %2").arg(
                     value).arg(square9Nb));
         break;
      case 1:
         treatOnlyOne(*_histogramValueForSquare9[square9Nb][value - 1].begin(),
               value);
         break;
      default:
         break;
      }
   }
}

void RulesManager::treatOnlyOne(SquareModel* squareModel, quint8 value)
{
   if (!squareModel->doesValueBeFound())
   {
      squareModel->setValue(value);
      for (quint8 v = 1; v <= GRID_SIZE; ++v)
      {
         if (v != value)
         {
            treatOnlyOneInLine(squareModel, v);
            treatOnlyOneInColumn(squareModel, v);
            treatOnlyOneInSquare9(squareModel, v);
         }
      }
      _histogramValueForLine[squareModel->getLineNb()][value - 1].clear();
      _histogramValueForColumn[squareModel->getColumnNb()][value - 1].clear();
      _histogramValueForSquare9[squareModel->getSquare9Nb()][value - 1].clear();
      _squareToTreatStack.push_front(squareModel);
   }
}

void RulesManager::printGrid()
{
   for (quint8 i = 0; i < GRID_SIZE; ++i)
   {
      if (i % 3 == 0)
      {
         qDebug() << "-------------------";
      }
      QString line;
      for (quint8 j = 0; j < GRID_SIZE; ++j)
      {
         if (j % 3)
         {
            line += " ";
         }
         else
         {
            line += "|";
         }
         if (_gridModel.getSquareModel(i, j)->doesValueBeFound())
         {
            line += QString::number(
                  _gridModel.getSquareModel(i, j)->getValue());
         }
         else
         {
            line += " ";
         }
      }
      line += "|";
      qDebug() << qPrintable(line);
   }
   qDebug() << "-------------------";
   QTextStream s(stdin);
//QString value = s.readLine();
}

void RulesManager::clear()
{
   _gridModel.clear();
   _squareToTreatStack.clear();

   _histogramValueForLine.clear();
   _histogramValueForColumn.clear();
   _histogramValueForSquare9.clear();
   _histogramValueForLine.resize(GRID_SIZE);
   _histogramValueForColumn.resize(GRID_SIZE);
   _histogramValueForSquare9.resize(GRID_SIZE);
   for (quint8 i = 0; i < GRID_SIZE; ++i)
   {
      _histogramValueForLine[i].resize(GRID_SIZE);
      _histogramValueForColumn[i].resize(GRID_SIZE);
      _histogramValueForSquare9[i].resize(GRID_SIZE);
      for (quint8 j = 0; j < GRID_SIZE; ++j)
      {
         _histogramValueForLine[i][j].reserve(GRID_SIZE);
         _histogramValueForColumn[i][j].reserve(GRID_SIZE);
         _histogramValueForSquare9[i][j].reserve(GRID_SIZE);
      }
   }

   for (quint8 lineNb = 0; lineNb < GRID_SIZE; ++lineNb)
   {
      for (quint8 columnNb = 0; columnNb < GRID_SIZE; ++columnNb)
      {
         for (quint8 value = 1; value <= GRID_SIZE; ++value)
         {
            SquareModel* squareModel = _gridModel.getSquareModel(lineNb,
                  columnNb);
            _histogramValueForLine[lineNb][value - 1].insert(squareModel);
            _histogramValueForColumn[columnNb][value - 1].insert(squareModel);
            _histogramValueForSquare9[squareModel->getSquare9Nb()][value - 1].insert(
                  squareModel);
         }
      }
   }
}
